CloudFront リアルタイムログからエッジロケーション名をAthena で抽出する
こんにちは、なおにしです。
CloudFront にアクセスした際にリクエストを処理したエッジロケーションをリアルタイムに知りたくなったのでやってみました。
はじめに
CloudFrontの標準ログ(アクセスログ)がS3に配信されるタイミングはドキュメントで以下のように記載されています。
CloudFront は、ディストリビューションの標準ログを 1 時間に最大で数回配信します。一般的に、ログファイルには、一定期間内に CloudFront が受信したリクエストに関する情報が含まれています。CloudFront は通常、その期間のログファイルを、ログに書き込まれたイベントの発生から 1 時間以内に Amazon S3 バケットに配信します。ただし、ある期間のログファイルエントリの一部またはすべてが、最大で 24 時間遅れることもあります。
リクエストを処理してから確認できるまでにかなり時間を要することがあるので、リアルタイムで確認したいという用途には向いていません。
そういった時のために、CloudFrontではリアルタイムログをサポートしています。
リアルタイムログの配信タイミングについてはドキュメントに以下の記載があります。
ログはリクエストを受信してから数秒以内に配信されます
一方、CloudFront で特定リクエストを処理したエッジロケーションを確認する方法については以下の記事でまとまっています。
今回はcurlでレスポンスヘッダーを確認するというよりも、実際の処理で使用された通信に関するエッジロケーションを確認したかったので、リアルタイムログでx-edge-location
フィールドを確認することでエッジロケーションを特定してみます。
やってみた
リアルタイムログの出力設定
CloudFront ディストリビューションを作成する際、リアルタイムログについては有効化する項目がありません。作成済みのディストリビューションで標準ログを有効化していた場合は、以下のように[テレメトリー] - [ログ]の項目でリアルタイムログが無効の状態で登録されています。こちらの画面から[リアルタイム設定]を行います。
こちらがリアルタイムログの設定画面ですが、出力先は Amazon Kinesis Data Streams となっており、標準ログのように直接S3に出力することはできません。
このため、S3にリアルタイムログを出力するためには以下の準備をしておく必要があります。
-
リアルタイムログを出力するS3バケットの作成
-
CloudFrontからのリアルタイムログ出力先となるKinesis Data Streams のデータストリーム作成
-
データストリームをS3バケットに出力先するためのKinesis Data Firehose のストリーム作成
詳細な手順については以下の記事にまとまっていましたので、上記の準備については割愛します。
基本は上記手順と同じくAWSマネジメントコンソール上のデフォルト設定としましたが、Firehose ストリームのみ以下のとおりプレフィックスタイムゾーンを「Asia/Tokyo」に変更しました。
それでは準備が終わった状態で改めてリアルタイムログの設定を行います。
今回は処理に使われているエッジロケーションを確認したかったので、以下7つをリアルタイムログ設定に含めるフィールドとして選択してみます。
フィールド名 | 説明 |
---|---|
timestamp | エッジサーバーがリクエストへの応答を終了した日時 |
c-ip | リクエスト元のビューワーの IP アドレス (192.0.2.183 または 2001:0db8:85a3::8a2e:0370:7334 など)。ビューワーが HTTP プロキシまたはロードバランサーを使用してリクエストを送った場合、このフィールドの値はプロキシまたはロードバランサーの IP アドレスです |
x-edge-location | リクエストを処理したエッジロケーション。各エッジロケーションは、3 文字コードと、割り当てられた任意の数字で識別されます (例: DFW3)。通常、この 3 文字コードは、エッジロケーションの地理的場所の近くにある空港の、国際航空運送協会 (IATA) の空港コードに対応します。(これらの略語は今後変更される可能性があります) |
x-edge-request-id | リクエストを一意に識別する不透明な文字列。CloudFront では、この文字列を x-amz-cf-id レスポンスヘッダーでも送信します |
x-host-header | CloudFront ディストリビューションのドメイン名 (d111111abcdef8.cloudfront.net など) |
x-forwarded-for | ビューワーが HTTP プロキシまたはロードバランサーを使用してリクエストを送信した場合、c-ip フィールドの値はプロキシまたはロードバランサーの IP アドレスです。この場合、このフィールドはリクエスト元のビューワーの IP アドレスです |
c-country | ビューワーの IP アドレスによって決定される、ビューワーの地理的位置を表す国コード |
なお、リアルタイムログの各フィールドに関する説明は以下にまとまっています。
リアルタイムログの設定が有効になりました。
設定時点ではまだ対象のS3バケットには何も存在していませんでした。このためCloudFrontに実際にアクセスしてみます。
今回は以前の記事で使用した簡易な静的サイトをホストしているS3バケットをオリジンとしたCloudFront ディストリビューションを対象にリアルタイムログを設定しています。
S3バケットを確認すると以下のとおり出力されています。サイトにアクセスしてからS3バケットでログを確認できるようになるまでは5分ほどかかりました。
Athenaで抽出
保存されたリアルタイムログをAthenaでクエリしてみます。テーブル作成のクエリはドキュメントに記載があります。
上記を参考に、今回は取得対象として設定したフィールドのみに絞ってテーブルを作成するので、以下のクエリを実行します。パーティション射影の設定も追加してみました。また、timestamp
フィールドはUNIX時間表記かつ小数点以下にミリ秒がついている形式(例:1724654289.993)だったため、FLOAT型を指定しています。
CREATE EXTERNAL TABLE IF NOT EXISTS cloudfront_real_time_logs (
`timestamp` FLOAT,
c_ip STRING,
x_edge_location STRING,
x_edge_request_id STRING,
x_host_header STRING,
x_forwarded_for STRING,
c_country STRING
)
PARTITIONED BY (`writtentime` string)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
LOCATION 's3://DOC-EXAMPLE-BUCKET/'
TBLPROPERTIES(
'skip.header.line.count'='2',
'projection.enabled' = 'true',
'projection.writtentime.type' = 'date',
'projection.writtentime.range' = 'NOW-1YEARS,NOW+9HOURS',
'projection.writtentime.format' = 'yyyy/MM/dd/HH',
'projection.writtentime.interval' = '1',
'projection.writtentime.interval.unit' = 'HOURS',
'storage.location.template' = 's3://DOC-EXAMPLE-BUCKET/${writtentime}/'
);
ちなみに私は毎回ハマってしまうのですが、PARTITIONED BY
句の記載位置には注意が必要です。TBLPROPERTIES
句にパーティション射影の詳細設定をまとめている都合上、以下のようについTBLPROPERTIES
句のすぐ上にPARTITIONED BY
句を記載したくなるのですが、このように記載すると以下のとおりエラーが発生します。
line 1:8: mismatched input 'EXTERNAL'. Expecting: 'MATERIALIZED', 'MULTI', 'OR', 'PROTECTED', 'ROLE', 'SCHEMA', 'TABLE', 'VIEW'
しかも調査のために出力されたエラー文字列でWeb検索すると別の原因(テーブル名をバッククォートでくくるなど。このような場合でも同様のエラーが出力されてしまうため)の方がおそらく先にヒットしてしまいますのでご注意ください。
テーブルが作成できたらクエリを実行して結果を取得してみます。以下のクエリを実行してみました。
SELECT
date_format(from_unixtime(timestamp, 'Asia/Tokyo'), '%Y/%m/%d %H:%i:%s') AS timestamp_converted_jst,
c_ip,
x_edge_location,
x_host_header,
x_forwarded_for,
c_country
FROM
cloudfront_real_time_logs
WHERE
writtentime = '2024/08/26/15'
ORDER BY timestamp DESC
良い感じに取得できました。timestamp
フィールドについてはUTC→JSTおよび表示形式の変更をクエリ内で行っています。
x-edge-location
フィールドを見ると「NRT12-P4」となっています。ドキュメントのとおりエッジロケーションの名称に国際航空運送協会 (IATA) の空港コードが使用されています。私の端末からのアクセスではNRT(成田国際空港)ということで日本のエッジロケーションが使用されていることが分かります。c-country
フィールド(国コード)もJPとなっています。
せっかくなのであまり使用する機会のないカナダリージョンにEC2インスタンスを起動してアクセスしてみました。
上記ではインスタンス起動後にGoogle Chromeをインストールしてヘッドレスブラウザとして使用しています。同様の操作を試される方はこちらもご参照ください。
カナダリージョンのEC2インスタンスから同じCloudFront ディストリビューションにアクセスした後、同様にAthenaでクエリを実行してみました。
今回はx-edge-location
フィールドが「YUL62-P1」、国コードは「CA(カナダ)」になっています。YULは「モントリオール・ピエール・エリオット・トルドー国際空港」のようです。カナダのケベック州ドルヴァルにある国際空港のようですね。
まとめ
CloudFrontのリアルタイムログからリクエストを処理したエッジロケーションを特定する方法をご紹介しました。今回はAthenaでクエリするところまで確認しましたので、同様の方法でリアルタイムログからその他のフィールドを抽出する用途でも参考にしていただければと思います。
本記事がどなたかのお役に立てれば幸いです。